package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private GoodsRepository repository;

    /**
     * 把一个spu转为一个Goods对象
     * @param spu
     * @return
     */
    public Goods buildGoods(SpuDTO spu){
        Long spuId = spu.getId();
        if(spuId == null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //1、spu下所有的sku
        List<SkuDTO> skuDTOS = itemClient.querySku(spuId);
        List<Map<String,Object>> skuList = new ArrayList<>();
        skuDTOS.forEach(skuDTO -> {
            Map<String,Object> sku = new HashMap<>();
            sku.put("id",skuDTO.getId());
            sku.put("images",skuDTO.getImages());
            sku.put("price",skuDTO.getPrice());
            sku.put("title",skuDTO.getTitle());
            skuList.add(sku);
        });

        //2、sku的价格集合
        Set<Long> price = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //3、搜索字段，包含名称、分类、品牌、等
        //3.1 分类
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(spu.getCategoryIds());
        String categoryName = categoryDTOS.stream().map(CategoryDTO::getName).collect(Collectors.joining());
        //3.2品牌
        BrandDTO brandDTO = itemClient.queryBrandId(spu.getBrandId());
        //3.3拼接
        String all = spu.getName() + categoryName + brandDTO.getName();

        //4、规格参数，包含key和value   =>specs
        Map<String,Object> specs = new LinkedHashMap<>();
        //获取key
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParam(null, spu.getCid3(), true);
        //获取value
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetail(spuId);
        //特有属性
        String specialSpec = spuDetailDTO.getSpecialSpec();
        Map<Long, List<String>> specialSpecValue = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<String>>>() {});
        //通用属性
        String genericSpec = spuDetailDTO.getGenericSpec();
        Map<Long, Object> genericSpecValue = JsonUtils.toMap(genericSpec, Long.class, Object.class);
        //添加进map
        specParamDTOS.forEach( specParamDTO -> {
            String key = specParamDTO.getName();
            Object value = null;
            //当generic为true时，将vlues赋值为通用属性，反之，将值赋予特有属性
            if(specParamDTO.getGeneric()){
                // 通用规格
                value = genericSpecValue.get(specParamDTO.getId());
            }else {
                // 特有规格
                value = specialSpecValue.get(specParamDTO.getId());
            }

            //判断是否是数字类型，是则通过方法将值变成区间值
            if(specParamDTO.getNumeric()){
                value = chooseSegment(value,specParamDTO);
            }

            specs.put(key,value);
        });

        //5、准备Goods对象
        Goods goods = new Goods();
        goods.setId(spu.getId()); //spuId
        goods.setBrandId(spu.getBrandId());//品牌id
        goods.setCategoryId(spu.getCid3());//分类id
        goods.setSubTitle(spu.getSubTitle());//副标题
        goods.setCreateTime(spu.getCreateTime().getTime());//创建时间
        goods.setSkus(JsonUtils.toString(skuList));// spu下的所有sku
        goods.setSpecs(specs);  // 规格参数，包含key和value
        goods.setPrice(price);  //sku价格的集合
        goods.setAll(all);  // 搜索字段，包含名称、分类、品牌等
        //返回Goods对象
        return goods;
    }

    public Map<String, List<?>> filter(SearchRequest sr) {
        // 返回的map
        Map<String, List<?>> map = new LinkedHashMap<>();
        String key = sr.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1、构建搜索对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2、拼接条件
        // 2.1 拼装搜索条件
        queryBuilder.withQuery(builderSearchKey(sr));
        // 2.2 分页条件: 如果size指定0，SpringDataElasticSearch就不会去查询，所以写个1
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 2.3 指定获取的内容
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));
        // 2.4 添加聚合条件
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        // 3、搜索结果
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 3.1 获取到这个：Aggregations
        Aggregations aggregations = aggregatedPage.getAggregations();
        // 3.2 取出分类的聚合
        Terms categoryTerm = aggregations.get(categoryAgg);
        // 3.3 处理分类的聚合
        List<Long> cids = handleCategoryFilter(categoryTerm, map);
        // 3.4 取出品牌的聚合
        Terms brandTerm = aggregations.get(brandAgg);
        // 3.5 处理品牌的聚合
        handleBrandFilter(brandTerm,map);

        //将规格参数过滤条件加入到filterParam中
        addSpecParamFilter(cids,builderSearchKey(sr),map);

        //返回结果
        return map;
    }

    /**
     * 添加规格参数进map
     * @param cids 分类id集合
     * @param mqb 搜索条件
     * @param map 存储的集合
     */
    private void addSpecParamFilter(List<Long> cids, QueryBuilder mqb, Map<String, List<?>> map) {
        //根据每个分类查询各自的规格参数
        cids.forEach(cid -> {
            List<SpecParamDTO> specParamDTOS = itemClient.querySpecParam(null, cid, true);
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            queryBuilder.withQuery(mqb);
            queryBuilder.withPageable(PageRequest.of(0,1));
            queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));
            specParamDTOS.forEach(specParamDTO -> {
                String aggName = specParamDTO.getName();
                String aggField = "specs." + aggName;
                queryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(aggField));
            });
            AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
            Aggregations aggregations = result.getAggregations();
            specParamDTOS.forEach(specParamDTO -> {
                String aggName = specParamDTO.getName();
                Terms terms = aggregations.get(aggName);
                List<String> specList = terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                map.put(aggName,specList);
            });
        });
    }

    private List<Long> handleCategoryFilter(Terms categoryTerm, Map<String, List<?>> map){
        List<? extends Terms.Bucket> buckets = categoryTerm.getBuckets();
        List<Long> cids = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            Long key = bucket.getKeyAsNumber().longValue();
            cids.add(key);
        }
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(cids);
        map.put("分类",categoryDTOList);
        return cids;
    }

    private void handleBrandFilter(Terms brandTerm,Map<String, List<?>> map){
//        List<? extends Terms.Bucket> buckets = brandTerm.getBuckets();
//        List<Long> cids = new ArrayList<>();
//        for (Terms.Bucket bucket : buckets) {
//            Long key = bucket.getKeyAsNumber().longValue();
//            cids.add(key);
//        }
//        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(cids);
//        map.put("品牌",brandDTOS);
        List<Long> brandIds = brandTerm.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(brandIds);
        map.put("品牌",brandDTOS);
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    //搜索所有商品
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {
        String key = searchRequest.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.组织条件
        // 2.0.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));
        // 2.1.搜索条件
        queryBuilder.withQuery(builderSearchKey(searchRequest));
        // 2.2.分页条件
        int page = searchRequest.getPage() - 1;
        Integer size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page,size));
        // 3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.解析分页数据
        long total = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> content = result.getContent();
        // 4.2.转换DTO
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(content, GoodsDTO.class);
        // 5.封装并返回
        return new PageResult<>(total,totalPages,goodsDTOS);
    }
    
    private QueryBuilder builderSearchKey(SearchRequest sr){
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", sr.getKey()).operator(Operator.AND));
        Map<String, Object> filters = sr.getFilters();
        filters.entrySet().forEach(entry ->{
            String key = entry.getKey();
            Object value = entry.getValue();
            if("分类".equals(key)){
                key = "categoryId";
            }else if("品牌".equals(key)){
                key = "brandId";
            }else {
                key = "specs." + key;
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,value));
        });
        return boolQueryBuilder;
    }

    //创建索引
    public void saveGoods(Long spuId) {
        SpuDTO spu = itemClient.querySpuById(spuId);
        Goods goods = buildGoods(spu);
        repository.save(goods);
    }

    //删除索引
    public void deleteGoods(Long spuId){
        repository.deleteById(spuId);
    }
}
